#include "GLWidget.h"

#include <stdlib.h>
#define _USE_MATH_DEFINES 1
#include <cmath>
#include <QInputDialog>
#include "Directions.h"
#include <QMessageBox>

// Necessary in Arch
#include <GL/glut.h>

//#include "AmbientOcclusion.h"



// Constructora amb format per defecte
GLWidget::GLWidget(QWidget * parent):QGLWidget(parent)
{
    // per rebre events de teclat
    setFocusPolicy(Qt::ClickFocus);

    connect( &timerRender, SIGNAL(timeout()), this, SLOT(updateGL()) );
    timerRender.start(0);
    DoingInteractive = NONE;
    lighting = true;
    renderFilled = true;
    acceleration = false;
    renderBVH = false;
    renderMode = AMBIENT_OCCLUSION;
    detectCorners = false;
    renderLevelBVH = 0;
    cerr << "       Practica 4 VA, QT2012" << endl << endl;
    cerr << "Autors: Carlos Garcia Marquez" << endl;
    cerr << "        Brian Carvajal Meza" << endl;

    help();
}

// initializeGL() - Aqui incluim les inicialitzacions del contexte grafic.
void GLWidget::initializeGL()
{
    glClearColor(0.35f, 0.35f, 0.6f, 1.0f);
    glEnable(GL_DEPTH_TEST);

    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    // dimensions escena i camera inicial
    escena.Init();
    emit UpdatedVertexs(escena.GetVertexSize());
    computeCameraInicial();
}

void GLWidget::setModelview()
{
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0,0,-dist);
    glRotatef(-angleZ,0,0,1);
    glRotatef(angleX,1,0,0);
    glRotatef(-angleY,0,1,0);
    glTranslatef(-VRP.x,-VRP.y,-VRP.z);
}

void GLWidget::setProjection()
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(anglecam, ra, anterior, posterior);
}

void GLWidget::computeCameraInicial()
{
    double radi;
    Point centreEscena;

    centreEscena = escena.Center();
    radi = escena.RadiEscena();

    // mirem cap al centre del terra de l'escena
    VRP = centreEscena;

    dist = 2*radi;
    anglecam = 60; // 2 * asin (radi/2*radi)
    anterior = 0.01;//radi;
    posterior = 1000.; //3*radi;

    // inicialitzem els angles per a veure-ho des d'un cert punt de vista
    angleX = 30;
    angleY = 45;
    angleZ = 0;

    ra = (float)width()/(float)height();
}

// paintGL() - callback cridat cada cop que cal refrescar la finestra.
void GLWidget::paintGL( void )
{
    // Esborrem els buffers
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    setProjection();
    setModelview();

    // Dibuixar eixos aplicacio
    glDisable(GL_LIGHTING);
    glBegin(GL_LINES);
    glColor3f(1,0,0);
    glVertex3f(0,0,0);
    glVertex3f(20,0,0); // X
    glColor3f(0,1,0);
    glVertex3f(0,0,0);
    glVertex3f(0,20,0); // Y
    glColor3f(0,0,1);
    glVertex3f(0,0,0);
    glVertex3f(0,0,20); // Z
    glEnd();


    if (renderBVH)
        escena.RenderBVH(renderLevelBVH);

    if (lighting)
        glEnable(GL_LIGHTING);
    else
        glDisable(GL_LIGHTING);

    // Pintem els objectes de l'escena.
    escena.Render(renderMode);

}

// resizeGL() - Cridat quan es canvia el tamany del viewport.
void GLWidget::resizeGL (int width, int height)
{
    glViewport (0, 0, width, height);
    ra = (float)width/(float)height;
    setProjection();
}

// help() - Surt per la terminal des de la que hem
// engegat el programa. En la versio amn interficie
// hauria de crear una nova finestra amb la informacio...
void GLWidget::help( void )
{
    cout << endl <<" -----------------[Ajuda]---------------- " << endl;
    cout << "   Tecles definides: \n";
    cout << "   a         Calcular oclusio ambient" << endl;
    cout << "   b         Calcular obscurancies" << endl;
    cout << "   c         Activar/desactivar deteccio de cantonades amb obscurancies" << endl;
    cout << "   j         Activar/desactivar pintat de la jerarquia BHV" << endl;
    cout << "   UP/DOWN   Augmentar/disminuir el nivell a pintar del BHV" << endl;
    cout << "   l         Activar/desactivar iluminació" << endl;
    cout << "   f         Alternar entre pintat de poligons o filferros " << endl;
    cout << "   s         Pinta amb omplert de polgons" << endl;
    cout << "   r         Reinicia els parmetres de la cmara" << endl;
    cout << "   i         Informació" << endl;
    cout << "   h,H,?     Imprimir aquesta ajuda" << endl;

    cerr << " -----------------------------------------" << endl << endl;
}

void GLWidget::info(void) {
    cout << endl <<" ----------------[Informació]--------------- " << endl;

    if (renderMode == AMBIENT_OCCLUSION)
        cerr << "   Mode de renderitzat: Oclusio ambient" << endl;
    else if (detectCorners)
        cerr << "   Mode de renderitzat: Obscurancies amb detecció de cantonades" << endl;
    else
        cerr << "   Mode de renderitzat: Obscurancies" << endl;

    if (renderBVH)
        cerr << "   Renderitzat del BVH activada a nivell " << renderLevelBVH << endl;
    else
        cerr << "   Renderitzat del BVH desactivada" << endl;

    if (acceleration)
        cerr << "   Acceleració amb BHV activada" << endl;
    else
        cerr << "   Acceleració amb BHV desactivada" << endl;

    if (lighting)
        cerr << "   Iluminació: activada" << endl;
    else
        cerr << "   Iluminació: desactivada" << endl;

    if (renderFilled)
        cerr << "   Renderitzat amb poligons" << endl;
    else
        cerr << "   Renderitzat amb filferros" << endl;




    cerr << " --------------------------------------------" << endl << endl;
}

void GLWidget::setFacesPerNode(int facesPerNode)
{
    escena.facesPerNode = facesPerNode;
    escena.UpdateAllBVH();

}

/*--------------------
*
*  teclat()
*
*  callback per quan hom prem una tecla.
*
*/
void GLWidget::keyPressEvent(QKeyEvent *e)
{
    switch( e->key() ) {



    case Qt::Key_Up:
        renderLevelBVH++;
        cerr << "Nivel de renderitzat del BVH: " << renderLevelBVH << endl;
        updateGL();
        break;

    case Qt::Key_Down:
        if (renderLevelBVH > 0)
            renderLevelBVH--;
        cerr << "Nivel de renderitzat del BVH: " << renderLevelBVH << endl;
        updateGL();
        break;


    case Qt::Key_F:
        if (renderFilled)
            glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        else
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

        renderFilled = !renderFilled;
        info();
        updateGL();
        break;

    case Qt::Key_S:
        switch(renderMode) {
        case AMBIENT_OCCLUSION:
            renderMode = (detectCorners) ? OBSCURANCES_CORNERS : OBSCURANCES;
            break;
        default:
            renderMode = AMBIENT_OCCLUSION;
            break;
        }
        info();
        updateGL();
        break;

    case Qt::Key_C:
        detectCorners = !detectCorners;
        if (renderMode != AMBIENT_OCCLUSION) {
            renderMode = (detectCorners) ? OBSCURANCES_CORNERS : OBSCURANCES;
        }
        info();
        updateGL();
        break;

    case Qt::Key_I:
        info();
        break;

    case Qt::Key_R:
        computeCameraInicial();
        updateGL();
        break;

    case  Qt::Key_H  :
    case  '?'  :
        help();
        break;

    case Qt::Key_L:
        lighting = !lighting;
        info();
        updateGL();
        break;

    case Qt::Key_J:
        renderBVH = !renderBVH;
        info();
        updateGL();
        break;

    case Qt::Key_A: {
        bool ok;
        int nrays = QInputDialog::getInteger(this, tr("Oclusio ambient"),
                                             tr("Nombre de raigs"), 32, 0, 128, 1, &ok);

        if (!ok) return;


        cout << "Calculant ambient occlusion" << ((acceleration) ? " amb " : " sense ") << "acceleracio..." << endl;
        timerCalc.start();
        escena.UpdateAmbientOcclusion(nrays, acceleration);
        double elapsedTime = ((double)timerCalc.elapsed())/1000.0;
        cout << "done!" << endl;
        if (acceleration) {
            emit AmbienOclusionWithAccelerationCalculated(elapsedTime);
        } else {
            emit AmbientOclusionCalculated(elapsedTime);

        }
        info();
        updateGL();
        break;
    }


    case Qt::Key_B: {
        bool ok;
        int nrays = QInputDialog::getInteger(this, tr("Obscurancies"),
                                             tr("Nombre de raigs"), 32, 0, 128, 1, &ok);
        if (!ok) return;

        int dmax = QInputDialog::getInteger(this, tr("Obscurancies"),
                                             tr("Percentatge sobre la distancia maxima"), 100, 1, 100, 1, &ok);
        if (!ok) return;

        int rho = QMessageBox::information(this,tr("Obscurancies"),
                                           tr("Utilitzar funcio rho?"),"Si","No",0,1);

        cout << "Calculant obscurancies amb numRays= " << nrays << ", dmax= " << dmax << "%" << ((rho < 1)? " usant" : " sense usar") << " rho " << ((acceleration)?  "amb " : "sense ") << "acceleracio..." << endl;

        timerCalc.start();
        escena.UpdateObscurances(nrays, dmax, (rho < 1), acceleration);
        double elapsedTime = ((double)timerCalc.elapsed())/1000.0;
        if (acceleration)
            emit ObscurancesWithAccelerationCalculated(elapsedTime);
        else
            emit ObscurancesCalculated(elapsedTime);
        cout << "done!" << endl;
        info();
        updateGL();
        break;
    }


    default:
        e->ignore(); // el propaguem cap al pare...
    }
}

/*--------------------
* mousePressEvent()
*/
void GLWidget::mousePressEvent( QMouseEvent *e)
{
    xClick = e->x();
    yClick = e->y();

    if (e->button()&Qt::LeftButton &&
            ! (e->modifiers()&(Qt::ShiftModifier|Qt::AltModifier|Qt::ControlModifier))) {
        DoingInteractive = ROTATE;
    } else if (e->button()&Qt::LeftButton &&  e->modifiers() &Qt::ShiftModifier) {
        DoingInteractive = ZOOM;
    } else if (e->button()&&Qt::RightButton) {
        DoingInteractive = PAN;
    }
}

void GLWidget::wheelEvent(QWheelEvent * e) {
    if (e->delta() > 0) dist -= 0.5;
    else dist += 0.5;

    updateGL();
}

/*--------------------
*
* mouseReleaseEvent()
*
*/
void GLWidget::mouseReleaseEvent( QMouseEvent *)
{
    DoingInteractive = NONE;

}

/*--------------------
*
* mouseMoveEvent()
*
*/
void GLWidget::mouseMoveEvent(QMouseEvent *e)
{
    if (DoingInteractive == ROTATE) {
        angleY-=e->x()-xClick;
        angleX+=e->y()-yClick;
    } else if(DoingInteractive == ZOOM) {
        dist+=(e->y()-yClick)*0.01*escena.RadiEscena();
    } else if (DoingInteractive == PAN) {
        // Aqui cal el codi que modifica els parametres per a fer el pan
        double x = .1 * (-e->x() + xClick);
        double y = .1 * (e->y() - yClick);
        float m[4][4];
        glGetFloatv (GL_MODELVIEW_MATRIX, &m[0][0]);
        Point s, u;
        s.x = m[0][0];
        s.y = m[1][0];
        s.z = m[2][0];
        u.x = m[0][1];
        u.y = m[1][1];
        u.z = m[2][1];
        VRP = VRP + x * s + y * u;

        xClick = e->x();
        yClick = e->y();
    }

    // Actualitzar els plans de retallat

    xClick=e->x();
    yClick=e->y();

    updateGL();
}


Point GLWidget::getObs()
{
    double mat[16];
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    //glTranslatef(0,0,-dist);
    glRotatef(-angleZ,0,0,1);
    glRotatef(angleX,1,0,0);
    glRotatef(-angleY,0,1,0);
    //glTranslatef(-VRP.x,-VRP.y,-VRP.z);
    glGetDoublev(GL_MODELVIEW_MATRIX,mat);
    glPopMatrix();

    Vector zObs(mat[2],mat[6],mat[10]);
    cout << zObs << endl;
    return VRP+dist*zObs;
}



void GLWidget::toggleAcceleration(bool value)
{
    acceleration = value;
    info();
}




void GLWidget::LoadModel()
{
    QString fileName = QFileDialog::getOpenFileName(this, "Carregar model", "../models", "Object files (*.obj)");

    if (!fileName.isEmpty()) {
        escena.LoadObject(fileName);
        computeCameraInicial();
        updateGL();
        emit UpdatedVertexs(escena.GetVertexSize());
    }
}



